#include "PLYModel.h"
#include "Utility.h"
using namespace std;

Model3D::Model3D()
{
	boundingBox = BoundingBox();
	reset();
}

//int Model3D::NumPoints = 40000;
//int Model3D::NumVertices = 6000;

void Model3D::swapTexture()
{
	texToggle = !texToggle;
	cout<<"toggle: "<<texToggle;
}

void Model3D::reset()
{
	points.clear();
	colors.clear();
	vertices.clear();
	texCoords.clear();
	//textures.clear();
	memset(textures,0,sizeof(textures));
	boundingBox.reset();
	isTextured = false;
	texToggle = false;
}

void Model3D::init(string sfileName, string element)
{
	elementType = element;
	//read file & generate geometry
	if(element == "plane")
	{
		generatePlane();
		loadTexture("img\\stones.bmp","img\\grass.bmp");//vbo+shaders
		addTexture();
	}
	else
	{
		readFile(sfileName);
		//VAO VBO
		createVBO();
		//load Shaders
		initShader();
	}

	// sets the default color to clear screen
	glClearColor( 1.0, 1.0, 1.0, 1.0 ); // white background
	//glClearColor( 0.0, 0.0, 0.0, 0.0 ); // black background

	//projection
	setupProjection();
}


void Model3D::loadTexture(string sfileName1,string sfileName2)
{
	isTextured = true;
	cout<<"Loading texture\n"<<endl;

	if(!bmpread(sfileName1.c_str(), 0, &bitmap))
	{
		cout<<"Texture does not exist!\n"<<endl;
		return;
	}

	glGenTextures( 2, textures );

	glBindTexture( GL_TEXTURE_2D, textures[0] );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, bitmap.width, bitmap.height, 0, GL_RGB, GL_UNSIGNED_BYTE, bitmap.rgb_data );
	bmpread_free(&bitmap);

	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

	//for bmp2
	if(!bmpread(sfileName2.c_str(), 0, &bitmap))
	{
		cout<<"Texture does not exist!\n"<<endl;
		return;
	}

	glBindTexture( GL_TEXTURE_2D, textures[1] );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, bitmap.width, bitmap.height, 0, GL_RGB, GL_UNSIGNED_BYTE, bitmap.rgb_data );
	bmpread_free(&bitmap);

	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

	//glActiveTexture( GL_TEXTURE0 );
	//glBindTexture( GL_TEXTURE_2D, textures[0] );
}

void Model3D::addTexture()
{

	if(!isTextured)
		return;

	glActiveTexture( GL_TEXTURE0 );
	glBindTexture( GL_TEXTURE_2D, textures[0] );

	/*glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, bitmap.width, bitmap.height, 0, GL_RGB, GL_UNSIGNED_BYTE, bitmap.rgb_data );
	bmpread_free(&bitmap);

	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );*/

	// Create a vertex array object
	glGenVertexArrays( 1, &vao );
	glBindVertexArray( vao );

	// Create and initialize a buffer object
	glGenBuffers( 1, &bufferId );
	glBindBuffer( GL_ARRAY_BUFFER, bufferId );

	glBufferData( GL_ARRAY_BUFFER, points.size() * POINT_SIZE + texCoords.size() * sizeof(point2), NULL, GL_STATIC_DRAW );
	glBufferSubData( GL_ARRAY_BUFFER, 0, points.size() * sizeof(point4), &points[0] );
	glBufferSubData( GL_ARRAY_BUFFER, points.size() * sizeof(point4), texCoords.size() * sizeof(point2), &texCoords[0] );



	// Load shaders and use the resulting shader program
	programId = InitShader( "vshader2.glsl", "fshader2.glsl" );
	glUseProgram( programId );

	// set up vertex arrays
	GLuint vPosition = glGetAttribLocation( programId, "vPosition" );
	glEnableVertexAttribArray( vPosition );
	glVertexAttribPointer( vPosition, 4, GL_FLOAT, GL_FALSE, 0,
		BUFFER_OFFSET(0) );

	// Since our texture coordinates match our vertex positions, we
	//   can reuse the position data for our texture coordinates.
	GLuint vTexCoord = glGetAttribLocation( programId, "vTexCoord" ); 
	glEnableVertexAttribArray( vTexCoord );
	glVertexAttribPointer( vTexCoord, 2, GL_FLOAT, GL_FALSE, 0,
		BUFFER_OFFSET(points.size() * sizeof(point4)) );

	// Set our texture samples to the active texture unit
	glUniform1i( glGetUniformLocation(programId, "texture"), 0 );
	glBindTexture(GL_TEXTURE_2D, textures[0]);
}

void Model3D::createVBO()
{

	//create VAO
	//GLuint vao;
	glGenVertexArrays( 1, &vao );
	glBindVertexArray( vao );

	//create VBO
	glGenBuffers( 1, &bufferId );
	glBindBuffer( GL_ARRAY_BUFFER, bufferId );

	glBufferData( GL_ARRAY_BUFFER, points.size() * POINT_SIZE + colors.size() * POINT_SIZE, NULL, GL_STATIC_DRAW );
	glBufferSubData( GL_ARRAY_BUFFER, 0, points.size() * POINT_SIZE, &points[0] );
	glBufferSubData( GL_ARRAY_BUFFER, points.size() * POINT_SIZE, colors.size() * POINT_SIZE , &colors[0] );

}

void Model3D::initShader()
{
	//load shaders
	programId = InitShader( "vshader1.glsl", "fshader1.glsl" );
	glUseProgram( programId );

	glBindVertexArray( vao );
	glBindBuffer( GL_ARRAY_BUFFER, bufferId );

	// set up vertex arrays
	GLuint vPosition = glGetAttribLocation( programId, "vPosition" );
	glEnableVertexAttribArray( vPosition );
	glVertexAttribPointer( vPosition, 4, GL_FLOAT, GL_FALSE, 0,
		BUFFER_OFFSET(0) );

	GLuint vColor = glGetAttribLocation( programId, "vColor" ); 
	glEnableVertexAttribArray( vColor );
	glVertexAttribPointer( vColor, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(points.size() * POINT_SIZE) );
}

void Model3D::triangle(int a, int b, int c)
{
	//colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[a]; 
	points.push_back(vertices[a]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
	//colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[b]; 
	points.push_back(vertices[b]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
	//colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[c]; 
	points.push_back(vertices[c]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
}

void Model3D::quad( int a, int b, int c, int d )
{
	//colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[a]; 
	points.push_back(vertices[a]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
	//colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[b]; 
	points.push_back(vertices[b]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
	//colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[c]; 
	points.push_back(vertices[c]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
	//colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[a]; 
	points.push_back(vertices[a]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
	// colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[c]; 
	points.push_back(vertices[c]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
	// colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); //points[Index] = vertices[d]; 
	points.push_back(vertices[d]);colors.push_back(color4( 0.0, 1.0, 0.0, 1.0 ));
	Index++;
}

void Model3D::generateCube()
{
	//cube vertices
	vertices[0] = point4( -0.5, -0.5,  0.5, 1.0 );
	vertices[1] = point4( -0.5,  0.5,  0.5, 1.0 );
	vertices[2] = point4(  0.5,  0.5,  0.5, 1.0 );
	vertices[3] = point4(  0.5, -0.5,  0.5, 1.0 );
	vertices[4] = point4( -0.5, -0.5, -0.5, 1.0 );
	vertices[5] = point4( -0.5,  0.5, -0.5, 1.0 );
	vertices[6] = point4(  0.5,  0.5, -0.5, 1.0 );
	vertices[7] = point4(  0.5, -0.5, -0.5, 1.0 );

	//cube quads
	quad( 1, 0, 3, 2 );
	quad( 2, 3, 7, 6 );
	quad( 3, 0, 4, 7 );
	quad( 6, 5, 1, 2 );
	quad( 4, 5, 6, 7 );
	quad( 5, 4, 0, 1 );
}

void Model3D::generatePlane()
{

	const float height = 25.0f;
	const float width = 25.0f;
	const float attitude = 0.0f;

	const float repeat = 10.0f;

	vertices.push_back(point4(-width,attitude,-height,1.0f));//0: 0,1
	vertices.push_back(point4(-width,attitude,height,1.0f));//1: 0,0
	vertices.push_back(point4(width,attitude,height,1.0f));//2: 1,0
	vertices.push_back(point4(width,attitude,-height,1.0f));//3: 1,1

	//set points & texCoords
	points.push_back(vertices[0]); texCoords.push_back(point2(0.0,repeat));
	points.push_back(vertices[1]); texCoords.push_back(point2(0.0,0.0));
	points.push_back(vertices[2]); texCoords.push_back(point2(repeat,0.0));
	points.push_back(vertices[0]); texCoords.push_back(point2(0.0,repeat));
	points.push_back(vertices[2]); texCoords.push_back(point2(repeat,0.0));
	points.push_back(vertices[3]); texCoords.push_back(point2(repeat,repeat));

	//quad(3,2,1,0);
	Index = 6;

}


void Model3D::readFile(string sfileName)
{
	//clear
	reset();

	//open file
	const char* fileName = sfileName.c_str();
	fstream inStream;
	inStream.open(fileName,ios::in); //open file
	//int temp = inStream.tellg();
	if(inStream.fail())
	{
		cout<<"file open failure!"<<endl;
		return;
	}

	//read file
	string line;
	vector<string> splittedString;

	getline(inStream,line);//ply
	if(line!="Ply" && line!="ply")
	{
		return;//not present, exit
	}
	getline(inStream,line);//skip: format ascii
	getline(inStream,line);//read: element vertex #
	splittedString.clear();
	splittedString = Utility::split(line," ");
	numOfVertices = atoi(splittedString[2].c_str());

	for(int index = 0;index<3;index++)
	{
		getline(inStream,line);//skip: property*3
	}
	getline(inStream,line);//read: element face #
	splittedString.clear();
	splittedString = Utility::split(line," ");
	numOfPolygons = atoi(splittedString[2].c_str());

	getline(inStream,line);//skip: property
	getline(inStream,line);//skip: end_header

	//read vertices & calc bounding box
	for(int index = 0;index<numOfVertices;index++)
	{
		getline(inStream,line);
		splittedString.clear();
		splittedString = Utility::split(line," ");
		point4 vertex;
		vertex.x = atof(splittedString[0].c_str());
		vertex.y = atof(splittedString[1].c_str());
		vertex.z = atof(splittedString[2].c_str());
		vertex.w = 1.0f;

		vertices.push_back(vertex);

		//update boundingBox
		boundingBox.update(vertex);
	}

	if(elementType == "sphere")
	{
		sphereUniform();
	}
	else if(elementType == "cylinder")
	{
		cylinderUniform();
	}

	//clear Index for points
	Index = 0;

	//read polygons
	for(int index1 = 0;index1<numOfPolygons;index1++)
	{
		getline(inStream,line);
		splittedString.clear();
		splittedString = Utility::split(line," ");
		GLint vIndex[3];
		for(int index2 = 0;index2<3;index2++){
			vIndex[index2] = atoi(splittedString[index2+1].c_str());
		}
		triangle(vIndex[0],vIndex[1],vIndex[2]);
	}

	inStream.close();

}

void Model3D::sphereUniform()
{
	//center at 0,0,0
	float diameter = 0.2;
	point4 centerOffset = boundingBox.getCenter();
	point3 range = boundingBox.getRange();

	for(int index = 0;index<numOfVertices;index++)
	{
		vertices[index] -= centerOffset;
		vertices[index].x *= diameter/range.x;
		vertices[index].y *= diameter/range.y;
		vertices[index].z *= diameter/range.z;
		vertices[index].w = 1.0f;
	}

	//change boundingBox
	boundingBox.setMax(point3(diameter/2,diameter/2,diameter/2));
	boundingBox.setMin(point3(-diameter/2,-diameter/2,-diameter/2));

}

void Model3D::cylinderUniform()
{
	float diameter = 0.2f;
	int time = 5;// height/diameter
	point4 centerOffset = boundingBox.getCenter();
	point3 range = boundingBox.getRange();
	//rotate
	for(int index = 0;index < numOfVertices;index++)
	{
		vertices[index] -= centerOffset;
		vertices[index].x *= diameter/range.x;
		vertices[index].y *= diameter/range.y;
		vertices[index].z *= diameter*time/range.z;
		vertices[index].z -= diameter*time/2;

		point4 vertex = vertices[index];
		vertices[index].z = vertex.y;
		vertices[index].y = -vertex.z;

		vertices[index].w = 1.0f;
	}

	//change boundingBox
	boundingBox.setMax(point3(diameter/2,diameter*time,diameter/2));
	boundingBox.setMin(point3(-diameter/2,0.0f,-diameter/2));

}

void Model3D::setupProjection()
{
	//width/height
	Angel::mat4 perspectiveMat = Angel::Perspective((GLfloat)100.0, (GLfloat)512/(GLfloat)512, (GLfloat)0.1, (GLfloat) 100.0);

	float viewMatrixf[16];
	viewMatrixf[0] = perspectiveMat[0][0];viewMatrixf[4] = perspectiveMat[0][1];
	viewMatrixf[1] = perspectiveMat[1][0];viewMatrixf[5] = perspectiveMat[1][1];
	viewMatrixf[2] = perspectiveMat[2][0];viewMatrixf[6] = perspectiveMat[2][1];
	viewMatrixf[3] = perspectiveMat[3][0];viewMatrixf[7] = perspectiveMat[3][1];

	viewMatrixf[8] = perspectiveMat[0][2];viewMatrixf[12] = perspectiveMat[0][3];
	viewMatrixf[9] = perspectiveMat[1][2];viewMatrixf[13] = perspectiveMat[1][3];
	viewMatrixf[10] = perspectiveMat[2][2];viewMatrixf[14] = perspectiveMat[2][3];
	viewMatrixf[11] = perspectiveMat[3][2];viewMatrixf[15] = perspectiveMat[3][3];

	glUseProgram( programId );
	GLuint viewMatrix = glGetUniformLocationARB(programId, "projection_matrix");
	glUniformMatrix4fv( viewMatrix, 1, GL_FALSE, viewMatrixf);
}

void Model3D::drawModel(mat4 transform, vec4 color)
{
	//display matrix setup
	//_ctm = Angel::identity();
	_ctm = transform ;

	//modelView
	Angel::mat4 modelMat = Angel::identity();
	modelMat = _ctm ;
	float modelMatrixf[16];
	modelMatrixf[0] = modelMat[0][0];modelMatrixf[4] = modelMat[0][1];
	modelMatrixf[1] = modelMat[1][0];modelMatrixf[5] = modelMat[1][1];
	modelMatrixf[2] = modelMat[2][0];modelMatrixf[6] = modelMat[2][1];
	modelMatrixf[3] = modelMat[3][0];modelMatrixf[7] = modelMat[3][1];

	modelMatrixf[8] = modelMat[0][2];modelMatrixf[12] = modelMat[0][3];
	modelMatrixf[9] = modelMat[1][2];modelMatrixf[13] = modelMat[1][3];
	modelMatrixf[10] = modelMat[2][2];modelMatrixf[14] = modelMat[2][3];
	modelMatrixf[11] = modelMat[3][2];modelMatrixf[15] = modelMat[3][3];

	glUseProgram( programId );
	// set up projection matricies
	GLuint modelMatrix = glGetUniformLocationARB(programId, "model_matrix");
	glUniformMatrix4fv( modelMatrix, 1, GL_FALSE, modelMatrixf );

	if(isTextured)
	{
		glActiveTexture( GL_TEXTURE0 );
		if(texToggle){
			glBindTexture(GL_TEXTURE_2D, textures[1]);
		}
		else{
			glBindTexture(GL_TEXTURE_2D, textures[0]);
		}

		glBindVertexArray( vao );
		glBindBuffer( GL_ARRAY_BUFFER, bufferId );

		glBufferData( GL_ARRAY_BUFFER, points.size() * POINT_SIZE + texCoords.size() * sizeof(point2), NULL, GL_STATIC_DRAW );
		glBufferSubData( GL_ARRAY_BUFFER, 0, points.size() * sizeof(point4), &points[0] );
		glBufferSubData( GL_ARRAY_BUFFER, points.size() * sizeof(point4), texCoords.size() * sizeof(point2), &texCoords[0] );

		//draw
		glPolygonMode(GL_FRONT,GL_FILL);
		glEnable( GL_DEPTH_TEST );
		glDrawArrays( GL_TRIANGLES, 0, Index );//vec change
		glDisable( GL_DEPTH_TEST ); 

	}
	else
	{
		//change color
		for(int index = 0;index<colors.size();index++)
		{
			colors[index] = color;
		}

		glBindVertexArray( vao );
		glBindBuffer( GL_ARRAY_BUFFER, bufferId );

		glBufferData( GL_ARRAY_BUFFER, points.size() * POINT_SIZE + colors.size() * sizeof(point4), NULL, GL_STATIC_DRAW );
		glBufferSubData( GL_ARRAY_BUFFER, 0, points.size() * POINT_SIZE, &points[0] );
		glBufferSubData( GL_ARRAY_BUFFER, points.size() * POINT_SIZE, colors.size() * sizeof(point4) , &colors[0] );

		//draw
		glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
		glEnable( GL_DEPTH_TEST );
		glDrawArrays( GL_TRIANGLES, 0, Index );
		glDisable( GL_DEPTH_TEST ); 

	}

}